package multimedia;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

/**
 * Can replay .mp3 and .ogg files
 * Extends Thread and can be in replay or pause state
 * @author thomas
 */
public class AudioSongController extends Thread implements IAudioSongController {

    private boolean _isPaused = false;
    private boolean _isStopped = false;
    private boolean _isStoppable = false;
    
    private File _myFile;
    private AudioSongModel _myModel;
    private AudioPlaylistController _myHandler;
    
    private AudioInputStream _myStream;
    private AudioFormat _myFormat;
    private SourceDataLine _myLine = null;
    

    /**
     * Constructor of controlleur
     * @param pathFile                          Path to files to read
     * @throws FileNotFoundException            If files is not found
     * @throws IOException                      
     * @throws UnsupportedAudioFileException    Only OGG and MP3 is available
     */
    public AudioSongController(String pathFile, AudioPlaylistController handler) throws FileNotFoundException, IOException, UnsupportedAudioFileException {
        
        super();
        
        // Check if file exist and if extensions are accepted
        _myFile = new File(pathFile);
        if (!_myFile.exists()) {
            throw new FileNotFoundException();
        } else if (!pathFile.endsWith(".ogg") && !pathFile.endsWith(".mp3")) {
            throw new UnsupportedAudioFileException();
        }
        
        _myHandler = handler;
        
        initialiseFile();
        initialiseModel();
        
        System.out.println(_myModel.getTitle() + " --> Chargement terminé");
    }

    /**
     * Initalise the stream, load metadata from file
     * @throws UnsupportedAudioFileException
     * @throws IOException 
     */
    private void initialiseFile() throws UnsupportedAudioFileException, IOException {

        AudioInputStream in = AudioSystem.getAudioInputStream(_myFile);
        AudioFormat baseFormat = in.getFormat();

        
        // Open format of song file
        _myFormat = new AudioFormat(
                AudioFormat.Encoding.PCM_SIGNED,
                baseFormat.getSampleRate(),
                16,
                baseFormat.getChannels(),
                baseFormat.getChannels() * 2,
                baseFormat.getSampleRate(),
                false);
        _myStream = AudioSystem.getAudioInputStream(_myFormat, in);
        
        
        // Open data line to read the audio file
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, _myFormat);
        try {
            _myLine = (SourceDataLine) AudioSystem.getLine(info);
            _myLine.open(_myFormat);
        } catch (LineUnavailableException ex) {
            Logger.getLogger(AudioSongController.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        
        // Start the line and prepare it to send I/O packet
        if (_myLine != null) {
            _myLine.start();
        }
    }
    
    /**
     * Generate model of a song
     * @throws UnsupportedAudioFileException
     * @throws IOException 
     */
    private void initialiseModel() throws UnsupportedAudioFileException, IOException {
        
        AudioFileFormat baseFileFormat = AudioSystem.getAudioFileFormat(_myFile);
        Map properties = baseFileFormat.properties();
        
        _myModel = new AudioSongModel();
        _myModel.setPath(_myFile.getAbsolutePath());
        _myModel.setTitle((String) properties.get("title"));
        _myModel.setArtist((String) properties.get("author"));
        _myModel.setAlbum((String) properties.get("album"));
        _myModel.setDate((String) properties.get("date"));
        _myModel.setComment((String) properties.get("comment"));
        _myModel.setCopyright((String) properties.get("copyright"));
        _myModel.setDuration(0);
        _myModel.setTotalDuration(Math.round(((((Long) properties.get("duration")).longValue())) / 1000000));
    }
    
    /**
     * Launch reading of song
     * When a song is paused, this method resume the reading (not restart song)
     */
    @Override
    public void play() {
        System.out.println(_myModel.getTitle() + " --> Lecture");
        
        if (_isStoppable) {
            _isStoppable = false;
        }
        
        _isPaused = false;
        _isStopped = false;
        
        synchronized (this) {
            this.notify();
        }
    }
    
    /**
     * Toogle in pause state
     */
    @Override
    public void pause() {
        System.out.println(_myModel.getTitle() + " --> Pause");
        _isPaused = true;
    }
    
    /**
     * Restart the song
     * @throws UnsupportedAudioFileException
     * @throws IOException 
     */
    @Override
    public void restart() throws UnsupportedAudioFileException, IOException {
        initialiseFile();
        initialiseModel();
    }
    
    /**
     * Set stop to the song
     */
    @Override
    public void turnOff() {
        System.out.println(_myModel.getTitle() + " --> Stop");
        _isStopped = true;
        _isPaused = true;
    }
    
    /**
     * Stop and unload the song
     */
    @Override
    public void unload() {
        System.out.println(_myModel.getTitle() + " --> Quit");
        _isStoppable = true;
    }
    
    /**
     * Thread method, don't call it ! Use start() method and next play()
     */
    @Override
    public void run() {
        System.out.println(_myModel.getTitle() + " --> Run");
        
        if (_myLine != null) {
            
            byte[] data = new byte[4096];
            int nBytesRead = 0, nBytesWritten = 0;
            
            // End loop when stop is wanted or song is finished
            while (!_isStoppable && nBytesRead != -1) {
                
                // If song is stopped
                if (_isStopped) {
                    nBytesRead = 0;
                    nBytesWritten = 0;
                }
                
                // If song is paused
                if (_isPaused) {
                    synchronized (this) {
                        try {
                            this.wait();
                        } catch (InterruptedException ex) {
                            Logger.getLogger(AudioSongController.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
                
                try {
                    
                    // Read bits while it is possible - boolean show if song is played or in pause
                    nBytesRead = _myStream.read(data, 0, data.length);
                    if (nBytesRead != -1) {
                        nBytesWritten = _myLine.write(data, 0, nBytesRead);
                        _myModel.setDuration((int) _myLine.getMicrosecondPosition() / 1000000);
                    }
                    
                } catch (IOException ex) {
                    nBytesRead = -1;
                }
                
                // Sleep of 5 milliseconds to avoid cpu surcharge
                try {
                    Thread.sleep(5);
                } catch (InterruptedException ex) {
                    Logger.getLogger(AudioSongController.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            
            // Stop everything
            _myLine.drain();
            _myLine.stop();
            _myLine.close();
            try {
                _myStream.close();
            } catch (IOException ex) {
                Logger.getLogger(AudioSongController.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            if (_myHandler != null) {
                _myHandler.endOfSong();
            }
            
        }
    }

    /**
     * @return the AudioSongModel
     */
    public AudioSongModel getModel() {
        return _myModel;
    }

    /**
     * @param myModel the AudioSongModel to set
     */
    public void setModel(AudioSongModel myModel) {
        this._myModel = myModel;
    }

    /**
     * @return the _isActive
     */
    public boolean isActive() {
        return !_isPaused && !_isStopped;
    }

    
    /**
     * Test reading a song. Type start to launch song, play or pause are explicite, and quit for unload song
     * @param args 
     */
    public static void main(String[] args) {
        
        AudioSongController asc = null;
        try {
            //asc = new AudioSongController("/media/Data/Musique/Aerosmith/Nine Lives/01 - Nine Lives.ogg");
            asc = new AudioSongController("/media/Data/Musique/Ange/Le cimetière des arlequins/CD1/Track01.mp3", null);
        } catch (FileNotFoundException ex) {
            System.err.println("Le fichier n'existe pas.");
            ex.printStackTrace(System.err);
        } catch (IOException ex) {
            Logger.getLogger(AudioSongController.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedAudioFileException ex) {
            System.err.println("Le format de fichier n'est pas reconnu (mp3 ou ogg). Les plugins sont-ils bien importés ?");
            ex.printStackTrace(System.err);
        }
        Scanner sc = new Scanner(System.in);
        String msg = "";
        while (true) {
            
            msg = sc.nextLine();
            
            if (msg.equalsIgnoreCase("start")) {
                asc.start();
                asc.play();
            } else if (msg.equalsIgnoreCase("play")) {
                asc.play();
            } else if (msg.equalsIgnoreCase("pause")) {
                asc.pause();
            } else if (msg.equalsIgnoreCase("quit")) {
                asc.unload();
                break;
            }
        }
    }
}
